package com.lazyframework.security.admin.authentication.filter;

import com.lazyframework.security.admin.authentication.JwtAuthenticationFailureHandler;
import com.lazyframework.security.admin.authentication.JwtAuthenticationToken;
import io.jsonwebtoken.JwtException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * JWT认证过滤器，拦截request请求头中的Authorization进行校验和授权
 * <p>
 * Create by lazy in 2019.09.17
 */
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private AuthenticationManager authenticationManager;

    private String[] exclusions;

    private JwtAuthenticationFailureHandler authenticationFailureHandler;

    private final String DEFAULT_AUTHORIZATION_PARAMETER = "Authorization";

    private String authorizationParameter = DEFAULT_AUTHORIZATION_PARAMETER;

    public JwtAuthenticationFilter() {
        this("/**");
    }

    public JwtAuthenticationFilter(String... exclusions) {
        this.exclusions = exclusions;
    }

    public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws AuthenticationException, IOException, ServletException {
        if (dispenseAuthentication(request)) {
            filterChain.doFilter(request, response);
            return;
        }

        Authentication auth = null;
        String authorization = obtainAuthorization(request);

        if (!StringUtils.isEmpty(authorization)) {
            try {
                auth = authenticationManager.authenticate(new JwtAuthenticationToken(authorization));
            } catch (JwtException e) {
                unsuccessfulAuthentication(request, response, e);
                return;
            }
        }

        // 如果JWT认证为通过，继续执行，走匿名登录流程
        SecurityContextHolder.getContext().setAuthentication(auth);

        filterChain.doFilter(request, response);
    }

    public void setAuthorizationParameter(String authorizationParameter) {
        this.authorizationParameter = authorizationParameter;
    }

    public void setAuthenticationManager(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    public void setExclusions(String... exclusions) {
        this.exclusions = exclusions;
    }

    public void setAuthenticationFailureHandler(JwtAuthenticationFailureHandler authenticationFailureHandler) {
        this.authenticationFailureHandler = authenticationFailureHandler;
    }

    private boolean dispenseAuthentication(HttpServletRequest request) {
        for (String exclusion : exclusions) {
            if (new AntPathRequestMatcher(exclusion).matches(request)) {
                return true;
            }
        }
        return false;
    }

    private String obtainAuthorization(HttpServletRequest request) {
        return request.getHeader(authorizationParameter);
    }

    /**
     * JWT认证失败
     *
     * @param request
     * @param response
     * @param exception
     */
    private void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, JwtException exception)
            throws IOException, ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("JWT authentication failure!");
        }
        authenticationFailureHandler.onAuthenticationFailure(request, response, exception);
    }

}
